﻿using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using Communication.Enums;
using Communication.Help.Crc;
using Communication.StreamResource;
using Microsoft.Extensions.Logging;
using NLogHelp;

namespace Communication.Help;

/// <summary>
///     峙之弘源 Modbus RTU协议扩展
/// </summary>
public static class ModbusRtuAlterSzhy
{
    private const int WriteLength = 9;

    private const byte WriteRegisterSingle = 0x06;

    private const byte ReadRegisterBatch = 0x04;

    private static readonly ArrayPool<byte> ArrayPool = ArrayPool<byte>.Create();
    private static readonly Crc16 Crc16 = new(Crc16Mode.Modbus);

    /// <summary>
    ///     写单个数据
    /// </summary>
    /// <param name="configuration">通讯组件</param>
    /// <param name="address">通讯地址</param>
    /// <param name="device">设备类型</param>
    /// <param name="function">功能码</param>
    /// <param name="begin">寄存器地址</param>
    /// <param name="value">写入值</param>
    public static void Write(this IStreamResource configuration, byte address, byte device, byte function, ushort begin,
        ushort value)
    {
        var data = ArrayPool.Rent(WriteLength);
        try
        {
            var span = data.AsSpan(0, WriteLength);
            span[0] = address; //1  设备地址
            span[1] = device; //2   设备类型
            span[2] = function; //3  功能码
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(3, 2), begin); // 4-5   起始地址
            BinaryPrimitives.WriteUInt16BigEndian(span.Slice(5, 2), value); // 6-7    写入值  
            BinaryPrimitives.WriteUInt16LittleEndian(span[^2..], Crc16.ComputeChecksum(span[..^2])); //8-9    Crc校验
            configuration.Logger.LogInformation("发送数据：{数据}", NlogHexPackage.Create(data, WriteLength));
            configuration.Write(data, 0, WriteLength);
        }
        finally
        {
            ArrayPool.Return(data, true);
        }
    }

    /// <summary>
    ///     写单个模拟量
    /// </summary>
    /// <param name="configuration">通讯组件</param>
    /// <param name="address">设备地址</param>
    /// <param name="device">设备类型</param>
    /// <param name="register">寄存器地址</param>
    /// <param name="value">写入值</param>
    /// <returns></returns>
    public static bool WriteAnalogQuantity(this IStreamResource configuration, byte address, byte device,
        ushort register, ushort value)
    {
        configuration.DiscardInBuffer();
        configuration.Write(address, device, WriteRegisterSingle, register, value);
        var buffer = ArrayPool.Rent(WriteLength);
        try
        {
            configuration.ContinuousRead(buffer);
            string mse;
            if (buffer[0] == address)
            {
                if (buffer[1] == device)
                {
                    if (buffer[2] == WriteRegisterSingle)
                    {
                        var span = buffer.AsSpan();
                        var data = MemoryMarshal.Cast<byte, ushort>(span.Slice(3, 6));
                        if (BinaryPrimitives.ReverseEndianness(data[0]) == register)
                        {
                            if (BinaryPrimitives.ReverseEndianness(data[1]) == value)
                            {
                                if (data[2] == Crc16.ComputeChecksum(span[..7]))
                                {
                                    configuration.Logger.LogInformation("接收数据：{数据}",
                                        NlogHexPackage.Create(buffer, WriteLength));
                                    return true;
                                }
                                else
                                {
                                    mse = "Crc校验失败";
                                }
                            }
                            else
                            {
                                mse = "写入值不一致";
                            }
                        }
                        else
                        {
                            mse = "寄存器地址不一致";
                        }
                    }
                    else
                    {
                        mse = "功能码错误";
                    }
                }
                else
                {
                    mse = "设备类型不匹配";
                }
            }
            else
            {
                mse = "地址错误";
            }

            configuration.Logger.LogError("错误信息：{信息},接收数据：{数据}", mse, NlogHexPackage.Create(buffer, WriteLength));
            return false;
        }
        finally
        {
            ArrayPool.Return(buffer, true);
        }
    }

    /// <summary>
    ///     读取多个模拟量
    /// </summary>
    /// <param name="configuration">通讯组件</param>
    /// <param name="address">设备地址</param>
    /// <param name="device">设备类型</param>
    /// <param name="begin">起始地址</param>
    /// <param name="len">读取长度</param>
    /// <returns></returns>
    public static ushort[] ReadAnalogQuantity(this IStreamResource configuration, byte address, byte device,
        ushort begin, ushort len)
    {
        configuration.DiscardInBuffer();
        configuration.Write(address, device, ReadRegisterBatch, begin, len);
        var dataLength = len * 2;
        //1:设备地址  2:设备类型    3:功能码   4:特征码   5:字节数   6:当前帧数  7:总帧数  
        //X: 数据长度   X+1:Crc低    X+2:Crc高
        var length = 7 + dataLength + 2;
        var data = ArrayPool.Rent(length);
        try
        {
            var buffer = data.AsSpan(0, length);
            configuration.ContinuousRead(data, 0, 7);
            string mse;
            if (buffer[0] == address)
            {
                if (buffer[1] == device)
                {
                    if (buffer[2] == ReadRegisterBatch)
                    {
                        if (buffer[3] == 0x55)
                        {
                            if (buffer[4] == dataLength)
                            {
                                if (buffer is [_, _, _, _, _, 0, 0, ..])
                                {
                                    configuration.ContinuousRead(data, 7, length - 7);

                                    if (BitConverter.ToUInt16(buffer[^2..]) == Crc16.ComputeChecksum(buffer[..^2]))
                                    {
                                        configuration.Logger.LogInformation("接收数据：{数据}",
                                            NlogHexPackage.Create(data, length));
                                        var result = MemoryMarshal.Cast<byte, ushort>(buffer.Slice(7, len * 2));
                                        return result.Select(BinaryPrimitives.ReverseEndianness).ToArray();
                                    }
                                    else
                                    {
                                        mse = "Crc校验失败";
                                    }
                                }
                                else
                                {
                                    mse = "当前数据帧,总帧数错误";
                                }
                            }
                            else
                            {
                                mse = "数据长度不足";
                            }
                        }
                        else
                        {
                            mse = "特征码错误";
                        }
                    }
                    else
                    {
                        mse = "功能码错误";
                    }
                }
                else
                {
                    mse = "设备类型不匹配";
                }
            }
            else
            {
                mse = "地址错误";
            }

            configuration.Logger.LogError("接收数据：{信息}-{数据}", mse, NlogHexPackage.Create(data, length));
            return [];
        }
        finally
        {
            ArrayPool.Return(data, true);
        }
    }
}